﻿#region Using Directives

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;

#endregion

namespace GeekRule.ORM.DataMapping
{
    /// <summary>
    /// Converts data values
    /// </summary>
    public class DataTypeConverter
    {
        #region Private Members

        private static readonly Dictionary<Type, TypeConverter> _typeConverters = new Dictionary<Type, TypeConverter>();

        #endregion

        /// <summary>
        /// Converts a reader value to a non-nullable value type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static T GetNonNullableValueTypeValue<T>(IDataReader reader, string propertyName) where T : struct
        {
            object value = reader.GetValue(reader.GetOrdinal(propertyName));
            if (value == DBNull.Value)
            {
                if (typeof(T).IsGenericType && Nullable.GetUnderlyingType(value.GetType()) != null)
                {
                    return default(T);
                }
                else
                {
                    throw new Exception();
                }
            }
            else
            {
                if (value is T)
                {
                    return (T)value;
                }
                else
                {
                    if (!_typeConverters.ContainsKey(typeof(T)))
                    {
                        _typeConverters.Add(typeof(T), TypeDescriptor.GetConverter(typeof(T)));
                    }

                    if (!_typeConverters[typeof(T)].IsValid(value))
                    {
                        if (typeof(T).IsEnum)
                        {
                            return (T)Enum.Parse(typeof (T), value.ToString());
                        }
                        else
                        {
                            throw new Exception();
                        }
                    }
                    else
                    {
                        return (T)_typeConverters[typeof(T)].ConvertTo(value, typeof(T));
                    }
                }
            }
        }

        /// <summary>
        /// Indicates if an item is nullable
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool IsObjectNullable<T>(T obj) 
        {
            return (!typeof (T).IsValueType || obj == null);
        } 

        /// <summary>
        /// Converts a reader value to a as a value type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static T GetValueTypeValue<T>(IDataReader reader, string propertyName)
        {
            object value = reader.GetValue(reader.GetOrdinal(propertyName));
            if (value == DBNull.Value)
            {
                return default(T);
            }
            else
            {
                if (value is T)
                {
                    return (T)value;
                }
                else
                {
                    if (!_typeConverters.ContainsKey(typeof(T)))
                    {
                        _typeConverters.Add(typeof(T), TypeDescriptor.GetConverter(typeof(T)));
                    }

                    string valueToConvert = value.ToString();
                    if (!_typeConverters[typeof(T)].IsValid(valueToConvert))
                    {
                        if (typeof(T) == typeof(bool))
                        {
                            valueToConvert = (value.ToString() == "1" ? "true" : "false");
                        }                        
                    }

                    if (!_typeConverters[typeof(T)].IsValid(valueToConvert))
                    {
                        throw new ArgumentException(value + " is not valid for " + typeof(T));
                    }                    
                    else
                    {
                        return (T) _typeConverters[typeof (T)].ConvertFromInvariantString(valueToConvert.ToString());
                    }
                }
            }
        }

        /// <summary>
        /// Converts a reader value to a reference type value
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static T GetRefTypeValue<T>(IDataReader reader, string propertyName) where T : class
        {
            object value = reader.GetValue(reader.GetOrdinal(propertyName));
            if (value == DBNull.Value)
            {
                return null;
            }
            else
            {
                if (value is T)
                {
                    return (T)value;
                }
                else
                {
                    if (!_typeConverters.ContainsKey(typeof(T)))
                    {
                        _typeConverters.Add(typeof(T), TypeDescriptor.GetConverter(typeof(T)));
                    }

                    if (!_typeConverters[typeof(T)].IsValid(value))
                    {
                        throw new Exception();
                    }
                    else
                    {
                        return (T)_typeConverters[typeof(T)].ConvertFrom(value);
                    }
                }
            }
        }
    }
}
